package com.atguigu.tingshu.album.util;

import org.jetbrains.annotations.NotNull;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * title:
 * author: zgl
 * date: 2024/3/11
 * description：
 */
public class RedisLock implements Lock {
    //获取锁的起始时间
    private Long startTime;

    private RedisTemplate redisTemplate;

    private String lockName;

    private String uuid;

    private Integer expireTime = 30;

    public RedisLock(RedisTemplate redisTemplate, String lockName, String uuid,Integer expireTime) {
        this.startTime = System.currentTimeMillis();
        this.redisTemplate = redisTemplate;
        this.lockName = lockName;
        this.expireTime = expireTime;
        this.uuid = uuid;
    }

    public RedisLock(RedisTemplate redisTemplate, String lockName, String uuid) {
        this.startTime = System.currentTimeMillis();
        this.redisTemplate = redisTemplate;
        this.lockName = lockName;
        this.uuid = uuid;
    }


    @Override
    public void lock() {
        this.tryLock();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        try {
            return this.tryLock(-1, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean tryLock(long time, @NotNull TimeUnit unit) throws InterruptedException {
        //判断是否超时
        if (time != -1 && time > 0) {
            long now = System.currentTimeMillis();
            if (now - startTime > unit.toMillis(time)) {
                throw new RuntimeException("获取锁超时！");
            }
        }
        String statement = " if redis.call('exists', KEYS[1]) == 0 " +
                "or redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +
                "then redis.call('hincrby', KEYS[1], ARGV[1], 1) redis.call('expire', KEYS[1], ARGV[2]) " +
                "return 1 " +
                "else " +
                "" +
                "return 0 end";
        Boolean flag = (Boolean) this.redisTemplate.execute(new DefaultRedisScript(statement, Boolean.class), Arrays.asList(lockName), uuid, expireTime);
        if (!flag) {
            //重试获取锁
            Thread.sleep(40);
            this.tryLock(time, unit);
        }
        reNewExpire();
        return true;
    }

    private void reNewExpire() {
        String statement = "if redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +
                "then " +
                "return redis.call('expire', KEYS[1], ARGV[2]) " +
                "else return 0 " +
                "end";
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {

                Boolean flag = (Boolean) redisTemplate.execute(new DefaultRedisScript(statement, Boolean.class), Arrays.asList(lockName), uuid, expireTime);
                if (flag) {
                    reNewExpire();
                }
            }
        }, expireTime * 1000 / 3);
    }

    @Override
    public void unlock() {
        String statement = "if redis.call('hexists', KEYS[1], ARGV[1]) == 0 " +
                "then " +
                "return nil " +
                "elseif " +
                "redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0 " +
                "then " +
                "return redis.call('del', KEYS[1]) " +
                "else " +
                "return 0 " +
                "end ";
        Long flag = (Long) this.redisTemplate.execute(new DefaultRedisScript(statement, Long.class), Arrays.asList(lockName), uuid);
        if (flag == null) {
            throw new IllegalMonitorStateException("不是锁的拥有者");
        }


    }

    @NotNull
    @Override
    public Condition newCondition() {
        return null;
    }
}
